/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.ibatis.scripting.xmltags;

import com.je.ibatis.builder.xml.CustomXMLMapperEntityResolver;
import com.je.ibatis.extension.keygen.MetaAutoKeyGenerator;
import com.je.ibatis.extension.keygen.MetaRouterKeyGenerator;
import com.je.ibatis.extension.keygen.MetaUUIDKeyGenerator;
import com.je.ibatis.extension.metadata.model.Column;
import com.je.ibatis.extension.metadata.model.Function;
import com.je.ibatis.extension.metadata.model.Id;
import com.je.ibatis.extension.metadata.model.Table;
import com.je.ibatis.extension.toolkit.Constants;
import com.je.ibatis.extension.toolkit.ParameterUtil;
import com.je.ibatis.session.CustomConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.ibatis.scripting.xmltags.*;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.je.ibatis.extension.toolkit.Constants.MAP_PARAMETER;

/**
 * MetaSqlNode
 *
 * @author wangmm@ketr.com.cn
 * @date 2019/11/20
 */
public class MetaSqlNode implements SqlNode {

    private static final String TENANT_ID_FIELD = "SY_TENANT_ID";
    private static final String TENANT_NAME_FIELD = "SY_TENANT_NAME";

    private static final Log LOGGER = LogFactory.getLog(MetaSqlNode.class);
    /**
     * mybatis 核心数据
     */
    private final CustomConfiguration configuration;
    /**
     * 判断语句校验器
     */
    private final ExpressionEvaluator evaluator;
    /**
     * 生成语句类型 update|insert|insertBatch|select|columns|pk-col|load|load-columns
     */
    private final TypeEnum type;
    /**
     * 判断语句,同if标签
     */
    private final String test;
    /**
     * 忽略的列
     */
    private final String ignore;
    /**
     * 表名，未指定则从调用参数中获取
     */
    private final String table;
    /**
     * 功能，未指定则从调用参数中获取
     */
    private final String func;
    /**
     * 动态列别名
     */
    private final String alias;

    public MetaSqlNode(CustomConfiguration configuration, String test, String type, String ignore, String table, String func, String alias) {
        this.evaluator = new ExpressionEvaluator();
        this.configuration = configuration;
        this.test = test;
        this.type = TypeEnum.parse(type);
        this.ignore = ignore;
        this.table = table;
        this.func = func;
        this.alias = alias;
    }

    @Override
    public boolean apply(DynamicContext context) {

        //支持Test语句
        if (!StringUtils.isEmpty(test) && !evaluator.evaluateBoolean(test, context.getBindings())) {
            return false;
        }
        //获取方法参数
        Object parameter = context.getBindings().get(DynamicContext.PARAMETER_OBJECT_KEY);
        //包装方法参数
        final MetaObject metaParam = configuration.newMetaObject(parameter);

        //获取functionCode
        String functionCode = this.func == null ? ParameterUtil.functionCode(metaParam) : this.func;
        //获取tableCode
        String tableCode = this.table == null ? ParameterUtil.tableCode(metaParam) : this.table;

        //功能对象
        Function function = null;
        //资源表对象
        Table table = null;

        //获取功能
        if (!StringUtils.isEmpty(functionCode)) {
            function = configuration.getMetaStatementBuilder().function(functionCode);
            table = function.getTable();
            tableCode = function.getTableCode();
        }
        //获取资源表
        if (!StringUtils.isEmpty(tableCode)) {
            table = configuration.getMetaStatementBuilder().table(tableCode);
        }

        //默认空语句
        SqlNode sqlNode = new TextSqlNode("");
        if (type == TypeEnum.insert) {
            Map<String, Object> bean = (Map<String, Object>) parameter;
            //插入语句
            sqlNode = insertScript(table, bean);
        } else if (type == TypeEnum.insertBatch) {
            //批量插入语句
            Map<String,Object> map = (Map<String, Object>) parameter;
            sqlNode = insertBatchScript(table, (List<Map<String, Object>>) map.get(Constants.LIST_ALIAS));
        } else if (type == TypeEnum.update) {
            Map<String, Object> bean = (Map<String, Object>) ((MapperMethod.ParamMap) parameter).get(Constants.MAP_ALIAS);
            //是否包含条件
            boolean hasWhere = false;
            if (metaParam.hasGetter(Constants.WRAPPER_ALIAS) && metaParam.getValue(Constants.WRAPPER_ALIAS) != null) {
                hasWhere = true;
            }
            //更新语句
            sqlNode = updateScript(table, bean, hasWhere);
        } else if (type == TypeEnum.select) {
            String selectColumns = ParameterUtil.selectColumns(metaParam);
            if (!(StringUtils.isEmpty(table) && StringUtils.isEmpty(selectColumns) && parameter instanceof Map)) {
                sqlNode = selectScript(table, selectColumns);
            }
        } else if (type == TypeEnum.columns) {
            //全部列 col,col2
            String selectColumns = ParameterUtil.selectColumns(metaParam);
            sqlNode = columnsScript(table, selectColumns);
        } else if (type == TypeEnum.pkCol) {
            //查询语句
            sqlNode = new StaticTextSqlNode(table.getId().getCode());
        } else if (type == TypeEnum.load) {
            if (function != null) {
                //查询功能加载语句 select col,col2.. from tablCcode
                sqlNode = loadScript(function);
            } else {
                String selectColumns = ParameterUtil.selectColumns(metaParam);
                if (!StringUtils.isEmpty(selectColumns)) {
                    sqlNode = selectScript(table, selectColumns);
                } else {
                    sqlNode = selectScript(table);
                }

            }
        } else if (type == TypeEnum.loadColumns) {
            //功能加载列 col,col2..
            sqlNode = loadColumnsScript(function);
        }

        return sqlNode.apply(context);
    }

    /**
     * 动态插入语句节点
     *
     * @param table Table对象
     * @param bean  插入数据
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode insertScript(Table table, Map<String, Object> bean) {
        //获取主键策略
        KeyGenerator keyGenerator = keyGenerator(table.getCode());
        //insert语句中是否忽略主键字段
        boolean ignoreIdColumn = false;
        if (keyGenerator instanceof MetaAutoKeyGenerator) {
            if (Objects.isNull(bean.get(table.getId().getCode())) || StringUtils.isEmpty(bean.get(table.getId().getCode()))) {
                ignoreIdColumn = true;
            }
        }

        //选取需要插入的列
        List<Column> columns = table.getColumnList();
        //去除租户字段，配合租户插件使用
        if (!bean.containsKey(TENANT_ID_FIELD)) {
            columns = columns.stream()
                    .filter(p -> !TENANT_ID_FIELD.equalsIgnoreCase(p.getCode()))
                    .filter(p -> !TENANT_NAME_FIELD.equalsIgnoreCase(p.getCode()))
                    .collect(Collectors.toList());
        }

        if (ignoreIdColumn && table.getId() != null) {
            columns = columns.stream().filter(p -> !table.getId().getCode().equalsIgnoreCase(p.getCode())).collect(Collectors.toList());
        }

        //格式化数据
        try {
            configuration.getMetaStatementBuilder().getParse().formData(table, bean);
        } catch (Exception e) {
            throw new ScriptingException("MetaDataParse.formData() Error.", e);
        }

        //拼接语句
        StringBuilder sqlScript = new StringBuilder("<script> insert into ").append(table.getCode());
        sqlScript.append(" <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\"> ");
        columns.forEach(p -> {
//            sqlScript.append("<if test=\"").append(p.getCode()).append(" != null\">").append(p.getCode()).append(",</if>");
            sqlScript.append(p.getCode()).append(",");
        });
        sqlScript.append("</trim>");
        sqlScript.append("<trim prefix=\"values (\" suffix=\")\" suffixOverrides=\",\">");
        columns.forEach(p -> {
//            sqlScript.append("<if test=\"").append(p.getCode()).append(" != null\">#{").append(p.getCode()).append("},</if>");
            sqlScript.append("#{").append(p.getCode()).append("},");
        });
        sqlScript.append("</trim>");
        sqlScript.append("</script>");
        return scriptToSqlNode(sqlScript);
    }

    /**
     * 动态批量插入语句节点
     *
     * @param table Table对象
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode insertBatchScript(Table table,List<Map<String,Object>> list) {
        //获取主键策略
        KeyGenerator keyGenerator = keyGenerator(table.getCode());
        //批量插入只支持UUID
        if (!(keyGenerator instanceof MetaUUIDKeyGenerator)) {
            throw new RuntimeException("batch insert only supported keyGenerator is MetaUUIDKeyGenerator!");
        }
        //选取需要插入的列
        List<Column> columns = table.getColumnList();
        if(list != null && !list.isEmpty() && !list.get(0).containsKey(TENANT_ID_FIELD)){
            columns = columns.stream()
                    .filter(p -> !TENANT_ID_FIELD.equalsIgnoreCase(p.getCode()))
                    .filter(p -> !TENANT_NAME_FIELD.equalsIgnoreCase(p.getCode()))
                    .collect(Collectors.toList());
        }
        //拼接语句
        StringBuilder sqlScript = new StringBuilder("<script> insert into ").append(table.getCode());
        sqlScript.append(" <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\"> ");
        columns.forEach(p -> {
            sqlScript.append(p.getCode()).append(",");
        });
        sqlScript.append("</trim> VALUES <foreach collection=\"" + Constants.LIST_ALIAS + "\" item=\"item\" index=\"index\" separator=\",\">");
        sqlScript.append(" <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
        columns.forEach(p -> {
            sqlScript.append("#{item.").append(p.getCode()).append("},");
        });
        sqlScript.append("</trim></foreach>");
        sqlScript.append("</script>");
        return scriptToSqlNode(sqlScript);
    }

    /**
     * 动态更新语句节点，需指定ID
     *
     * @param table    Table对象
     * @param bean     修改的数据
     * @param hasWhere 是否包含条件
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode updateScript(Table table, Map<String, Object> bean, boolean hasWhere) {
        Id id = table.getId();
        if (id == null || id.getCode() == null) {
            throw new RuntimeException(String.format("TableCode[%s] id not find!", table.getCode()));
        }
        //选取需要插入的列，忽略主键字段
        List<Column> columns = table.getColumnList().stream()
                .filter(p -> !table.getId().getCode().equalsIgnoreCase(p.getCode())).collect(Collectors.toList());

        //格式化数据
        try {
            configuration.getMetaStatementBuilder().getParse().formData(table, bean);
        } catch (Exception e) {
            throw new ScriptingException("MetaDataParse.formData() Error.", e);
        }

        //拼接语句
        StringBuilder sqlScript = new StringBuilder("<script> UPDATE ").append(table.getCode());
        sqlScript.append(" <trim prefix=\"set\" suffixOverrides=\",\"> ");
        // set ID=ID, xxx=#{xxx} ... 防止字段全部无值时出现  update table where ... 的错误,
        //当主键策略为自增时，达梦不支持修改主键
        //sqlScript.append(table.getId().getCode()).append("=").append(table.getId().getCode()).append(",");
        columns.forEach(p -> {
            sqlScript.append("<if test=\"").append(String.format(MAP_PARAMETER, p.getCode())).append("!=null\">").append(p.getCode()).append("=#{").append(String.format(MAP_PARAMETER, p.getCode())).append("},</if>");
        });
        sqlScript.append("</trim>");
        //如果不含条件 默认使用id作为条件
        if (!hasWhere) {
            sqlScript.append("WHERE ");
            sqlScript.append(id.getCode()).append("=#{").append(String.format(MAP_PARAMETER, id.getCode())).append("}");
        }
        sqlScript.append("</script>");
        return scriptToSqlNode(sqlScript);
    }

    /**
     * 拼接查询语句
     *
     * @param table 表元数据
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode selectScript(Table table) {
        return selectScript(table, null);
    }

    /**
     * 拼接查询语句
     *
     * @param table         表元数据
     * @param selectColumns 指定查询列
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode selectScript(Table table, String selectColumns) {
        List<SqlNode> contents = new ArrayList<>();
        contents.add(new StaticTextSqlNode("select "));
        if (StringUtils.isEmpty(selectColumns)) {
            contents.add(columnsScript(table, null));
        } else {
            List<String> select = Arrays.asList(selectColumns.replaceAll("\\s*", "").split(","));
            //所有列
            List<Column> columns = table.getColumnList();
            //拼接语句
            StringBuilder sqlScript = new StringBuilder();
            //过滤要查询的列
            columns.stream().filter(p -> select.contains(p.getCode())).forEach(p -> {
                if (!StringUtils.isEmpty(alias)) {
                    sqlScript.append(alias).append(".");
                }
                sqlScript.append(p.getCode()).append(",");
            });
            if (sqlScript.length() > 0) {
                sqlScript.setLength(sqlScript.length() - 1);
            }
            contents.add(new StaticTextSqlNode(sqlScript.toString()));
        }
        contents.add(new StaticTextSqlNode(" from " + table.getCode() + " " + (StringUtils.isEmpty(alias) ? "" : alias)));
        return new MixedSqlNode(contents);
    }

    /**
     * 获取表对应的列
     *
     * @param table Table对象
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode columnsScript(Table table, String selectColumns) {

        //所有列
        List<Column> columns = table.getColumnList();

        List<String> selectColumnsArray = null;
        if (!StringUtils.isEmpty(selectColumns)) {
            selectColumnsArray = Arrays.asList(selectColumns.split(","));
        }

        //过滤忽略列
        if (!StringUtils.isEmpty(ignore)) {
            List<String> ignores = Arrays.asList(ignore.split(","));
            columns = columns.stream().filter(p -> !ignores.contains(p.getCode())).collect(Collectors.toList());
        }

        //拼接语句
        StringBuilder sqlScript = new StringBuilder();
        List<String> finalSelectColumnsArray = selectColumnsArray;
        columns.forEach(p -> {
            if (finalSelectColumnsArray != null && !finalSelectColumnsArray.contains(p.getCode())) {
                return;
            }
            if (!StringUtils.isEmpty(alias)) {
                sqlScript.append(alias).append(".");
            }
            sqlScript.append(p.getCode()).append(",");
        });
        if (sqlScript.length() > 0) {
            sqlScript.setLength(sqlScript.length() - 1);
        }
        return new StaticTextSqlNode(sqlScript.toString());
    }

    /**
     * 获取功能对应的列
     *
     * @param function 功能元数据
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode loadScript(Function function) {
        List<SqlNode> contents = new ArrayList<>();
        contents.add(new StaticTextSqlNode("select "));
        contents.add(loadColumnsScript(function));
        contents.add(new StaticTextSqlNode(" from " + function.getTable().getCode() + (StringUtils.isEmpty(alias) ? "" : (alias + "."))));
        return new MixedSqlNode(contents);
    }

    /**
     * 获取功能对应的列
     *
     * @param function 功能元数据
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode loadColumnsScript(Function function) {

        //表所有列
        List<Column> columns = function.getTable().getColumnList();

        //过滤加载列
        if (!function.getLoadColumnNames().isEmpty()) {
            columns = columns.stream().filter(p -> function.getLoadColumnNames().contains(p.getCode())).collect(Collectors.toList());
        }

        //过滤忽略列
        if (!StringUtils.isEmpty(ignore)) {
            List<String> ignores = Arrays.asList(ignore.split(","));
            columns = columns.stream().filter(p -> !ignores.contains(p.getCode())).collect(Collectors.toList());
        }
        //拼接语句
        StringBuilder sqlScript = new StringBuilder();
        columns.forEach(p -> {
            if (!StringUtils.isEmpty(alias)) {
                sqlScript.append(alias).append(".");
            }
            sqlScript.append(p.getCode()).append(",");
        });
        if (sqlScript.length() > 0) {
            sqlScript.setLength(sqlScript.length() - 1);
        }
        return new StaticTextSqlNode(sqlScript.toString());
    }

    /**
     * 动态sql语句转SqlNode节点
     *
     * @param sqlScript 动态sql语句
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode scriptToSqlNode(StringBuilder sqlScript) {
        XPathParser parser = new XPathParser(sqlScript.toString(), false, configuration.getVariables(), new CustomXMLMapperEntityResolver());
        CustomXMLScriptBuilder builder = new CustomXMLScriptBuilder(configuration, parser.evalNode("/script"), null);
        return builder.parseDynamicTags();
    }

    /**
     * 获取资源表主键策略
     *
     * @param tableCode 表名
     * @return org.apache.ibatis.executor.keygen.KeyGenerator
     */
    private KeyGenerator keyGenerator(String tableCode) {
        return configuration.getKeyGenerator(tableCode + MetaRouterKeyGenerator.SELECT_KEY_SUFFIX);
    }

    private enum TypeEnum {

        /**
         * 插入语句
         */
        insert("insert"),
        /**
         * 批量插入
         */
        insertBatch("insertBatch"),
        /**
         * 更新语句
         */
        update("update"),
        /**
         * 查询
         */
        select("select"),
        /**
         * 查询,只获取列加载项
         */
        load("load"),
        /**
         * 表内所有列
         */
        columns("columns"),
        /**
         * 表主键列名
         */
        pkCol("pk-col"),
        /**
         * 功能加载列
         */
        loadColumns("load-columns");

        private String code;

        TypeEnum(String code) {
            this.code = code;
        }

        static TypeEnum parse(String code) {
            for (TypeEnum value : TypeEnum.values()) {
                if (value.getCode().equals(code)) {
                    return value;
                }
            }
            return null;
        }

        public String getCode() {
            return code;
        }
    }
}